#include <iostream>
#include <Eigen/Dense>

using namespace Eigen;
using namespace std;

int main()
{
	// Declaring a Matrix
	MatrixXd A(2,2); // 2x2 Matrix of elements of type double
	A(0,0) = 3;
	A(1,0) = 2.5;
	A(0,1) = -1;
	A(1,1) = A(1,0) + A(0,1); // Element wise addition and assignment

	// Printing the matrix to the output
	cout << "Here is the matrix A:\n" << A << endl;

	// Declaring a Vector 
	VectorXd B(2);
	B(0) = 4;
	B(1) = B(0) - 1; // Element wise subtraction and assignment

	// Printing the vector to the output
	cout << "Here is the vector B:\n" << B << endl;

	// Resizing a matrix
	MatrixXd C(2,5); // Declaring a 2x5 matrix
	C.resize(4,3); // Resizing to a 4x3 matrix
	cout << "The matrix C is of size " << C.rows() << "x" << C.cols() << endl;
	cout << "It has " << C.size() << " coefficients" << endl;
	
	// Resizing a vector
	VectorXd D(2); // Declaring a 2 element vector
	D.resize(5); // Resizing to a 5 element vector
	cout << "The vector D is of size " << D.size() << endl;
	cout << "As a matrix, D is of size " << D.rows() << "x" << D.cols() << endl;
	
	// Assignment and Resizing
	MatrixXf E(2,2);
	cout << "E is of size " << E.rows() << "x" << E.cols() << endl;
	MatrixXf F(3,3);
	E = F;
	cout << "E is now of size " << E.rows() << "x" << E.cols() << endl;

	// Addition and Subtraction

	Matrix2d G;
	G << 1, 2, 3, 4; // Another way to assign values to each element of the matrix

	MatrixXd H(2,2);
	H << 2, 3, 1, 4;

	cout << "G + H =\n" << G + H << endl;

	cout << "G - H =\n" << G - H << endl;

	cout << "Doing G += H;" << endl;
	G += H;
	cout << "Now G =\n" << G << endl;
	
	Vector3d I(1,2,3);
	Vector3d J(1,0,0);
	cout << "-I + J - I =\n" << -I + J - I << std::endl;

	// Scalar Multiplication and Division

	Matrix2d K;
	K << 1, 2, 3, 4;

	Vector3d L(1,2,3);

	cout << "K * 2.5 =\n" << K * 2.5 << endl;

	cout << "0.1 * L =\n" << 0.1 * L << endl;

	cout << "Doing L *= 2;" << endl;
	L *= 2;
	cout << "Now L =\n" << L << endl;

	// Transpose, Conjugation, Adjoint

	MatrixXcf M = MatrixXcf::Random(2,2);

	cout << "Here is the matrix M\n" << M << endl;

	cout << "Here is the matrix M^T\n" << M.transpose() << endl;

	cout << "Here is the conjugate of M\n" << M.conjugate() << endl; // For real matrices, conjugate() is a no-operation, and so adjoint() is equivalent to transpose().

	cout << "Here is the matrix M^*\n" << M.adjoint() << endl;	

	// Matrix-Matrix and Matrix-Vector Multiplication

	Matrix2d N;
	N << 1, 2, 3, 4;

	Vector2d O(-1,1), P(2,0);

	cout << "Here is N*N:\n" << N*N << endl;

	cout << "Here is N*O:\n" << N*O << endl;

	cout << "Here is O^T*N:\n" << O.transpose()*N << endl;

	cout << "Here is O^T*P:\n" << O.transpose()*P << endl;

	cout << "Here is O*P^T:\n" << O*P.transpose() << endl;

	// Dot Product and Cross Product

	Vector3d Q(1,2,3);
	Vector3d R(0,1,2);
	cout << "Dot product: " << Q.dot(R) << endl;

	double dp = Q.adjoint()*R; // automatic conversion of the inner product to a scalar
	cout << "Dot product via a matrix product: " << dp << endl;

	cout << "Cross product:\n" << Q.cross(R) << endl;

	// Computing Inverse and Determinant

	Matrix3f S;
	S << 1, 2, 1,
	2, 1, 0,
	-1, 1, 2;

	cout << "Here is the matrix S:\n" << S << endl;

	cout << "The determinant of S is " << S.determinant() << endl;

	cout << "The inverse of S is:\n" << S.inverse() << endl;

}
